package xyz.canran.project.algorithm.rate.limiter;



import java.util.concurrent.atomic.AtomicInteger;

/**
 * 滑动窗口限流计数器
 * @author canran
 */
public class SlidingWindowRateLimiter {

    private int qps;
    private long windowSize;
    private Integer windowCount;
    private WindowInfo[] windowArray;

    /**
     * 滑动窗口限流计数器
     * @param qps 时间间隔内限流数
     * @param windowSize 时间间隔，毫秒数
     */
    public SlidingWindowRateLimiter(int qps, long windowSize) {
        this.qps = qps;
        this.windowSize = windowSize;
        this.windowCount = 10;
        this.windowArray = new WindowInfo[windowCount];
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < windowCount; i++) {
            windowArray[i] = new WindowInfo(startTime, new AtomicInteger(0));
        }
    }

    /**
     * 返回限流结果
     * @return
     */
    public synchronized boolean tryAcquire() {
        long currentTimeMillis = System.currentTimeMillis();
        int currentIndex = (int)(currentTimeMillis % windowSize / (windowSize / windowCount));
        int sum = 0;
        for (int i = 0; i < windowArray.length; i++) {
            WindowInfo windowInfo = windowArray[i];
            if ((currentTimeMillis - windowInfo.getTime()) > windowSize) {
                windowInfo.getCounter().set(0);
                windowInfo.setTime(currentTimeMillis);
            }
            if (currentIndex == i && windowInfo.getCounter().get() < qps) {
                windowInfo.getCounter().incrementAndGet();
            }
            sum = sum + windowInfo.getCounter().get();
        }
        return sum <= qps;
    }


    private static class WindowInfo {
        /**
         * 窗口开始时间
         */
        private long time;

        /**
         * 计数器
         */
        private AtomicInteger counter;

        public WindowInfo(long time, AtomicInteger counter) {
            this.time = time;
            this.counter = counter;
        }

        public long getTime() {
            return time;
        }

        public void setTime(long time) {
            this.time = time;
        }

        public AtomicInteger getCounter() {
            return counter;
        }
        public void setCounter(AtomicInteger counter) {
            this.counter = counter;
        }
    }
}
